﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using System.Windows.Markup;
using System.Xml;

namespace Geometry
{
    public class GEngine
    {
        public GEngine()
        {
            this.Graph = new GObjectCollection {Origin};
            this.Generators = (from m in typeof(GObjectFactory).GetMethods(BindingFlags.Static | BindingFlags.Public)
                            let a = m.GetCustomAttributes(typeof(GObjectGeneratorAttribute), false)
                            where a != null && a.Length == 1
                            select new GObjectGenerator(m)).ToList();
            this.Snapshots = new GraphSnapshotCollection();
            CreateSnapshot("Origin");
        }

        public GObjectCollection Graph { get; private set; }

        public GraphSnapshotCollection Snapshots { get; private set; }

        private List<IGObject> m_Selected;

        public List<GObjectGenerator> Generators { get; private set; }

        public void HandleSelection(IList selection)
        {
            m_Selected = selection.Cast<IGObject>().ToList();
            this.Generators.ForEach(gog => gog.UpdateToSelection(m_Selected));
        }

        public static readonly RoutedUICommand Generate = new RoutedUICommand();
        public static readonly RoutedUICommand Delete = new RoutedUICommand("_Delete Selection", "Delete", typeof(GEngine),
            new InputGestureCollection() {
                new KeyGesture(Key.Delete)
            });
        public static readonly RoutedUICommand Clear = new RoutedUICommand("_Clear Selection", "Clear", typeof(GEngine),
            new InputGestureCollection()
                {
                    new KeyGesture(Key.E, ModifierKeys.Control)
                });
        public static readonly RoutedUICommand Save = new RoutedUICommand("_Save", "Save", typeof(GEngine),
            new InputGestureCollection()
                {
                    new KeyGesture(Key.S, ModifierKeys.Control)
                });
        public static readonly RoutedUICommand Load = new RoutedUICommand("_Load", "Load", typeof(GEngine),
            new InputGestureCollection()
                {
                    new KeyGesture(Key.L, ModifierKeys.Control)
                });
        public static readonly RoutedUICommand Undo = new RoutedUICommand("_Undo", "Undo", typeof(GEngine),
            new InputGestureCollection() {
                new KeyGesture(Key.Z, ModifierKeys.Control)
            });
        public static readonly RoutedUICommand Redo = new RoutedUICommand("_Redo", "Redo", typeof(GEngine),
            new InputGestureCollection() {
                new KeyGesture(Key.Y, ModifierKeys.Control)
            });
        public static readonly RoutedUICommand History = new RoutedUICommand();


        public readonly FreePoint Origin = GObjectFactory.CreateFreePoint();
        public bool DeleteItems(IList<IGObject> items)
        {
            if (items.Any(g => g.Dependents.Count != 0 && g.Dependents.Any(go => !items.Contains(go)))) return false;
            // assert: there are no items, outside the list, that depends on an item in the list
            var toBeRemoved = items.ToList();
            var removables = (from g in toBeRemoved
                        where g.Dependents.Count == 0
                        select g);
            // removing free items will release other items
            while (removables.Any())
            {
                // loop modifies toBeRemoved, so we evaluate removables first
                foreach (var g in removables.ToList())
                {
                    this.Graph.Remove(g);
                    g.Dispose();
                    toBeRemoved.Remove(g);
                }
            }
            CreateSnapshot(items.Count + " item(s) deleted");
            return true;
        }

        public void LoadGraph(string p)
        {
            using (var file = XmlReader.Create(p))
                LoadGraph(XamlReader.Load(file) as GObjectCollection);
            CreateSnapshot("Loaded " + Path.GetFileName(p));
        }

        private void LoadGraph(GObjectCollection loaded)
        {
            this.Graph.Clear();
            GObjectFactory.ClearNameRecords();
            foreach (var item in loaded)
            {
                GObjectFactory.RecordName(item, item.Name);
                var refs = (from prop in item.GetType().GetProperties()
                            let value = prop.GetValue(item, null) as PlaceHolder
                            where value != null
                            select new
                            {
                                Name = value.ElementName,
                                Setter = new Action<object>(v =>
                                {
                                    prop.GetSetMethod().Invoke(item, new object[] { v });
                                })
                            });
                foreach (var ph in refs)
                {
                    ph.Setter(loaded.Where(o => ph.Name.Equals(o.Name)).First());
                }
                this.Graph.Add(item);
            }
        }

        public void SaveGraph(string p)
        {
            var xws = new XmlWriterSettings {Indent = true};
            using (var file = XmlWriter.Create(p, xws))
            {
                XamlWriter.Save(this.Graph, file);
            }
        }

        public void MoveInHistory(int changeCount)
        {
            // TODO: since this snapshot method is inefficient, there is no need to optimize this process. just redo :)
            if (changeCount > 0)
            {
                var first = Snapshots.FirstOrDefault(gs => gs.Undone);
                if (first == null) return;
                var pos = Snapshots.IndexOf(first);
                pos += changeCount - 1;
                if (pos >= Snapshots.Count) pos = Snapshots.Count - 1;
                var loaded = XamlReader.Load(XmlReader.Create(new StringReader(Snapshots[pos].Data))) as GObjectCollection;
                LoadGraph(loaded);
                Snapshots.Where((gs, i) => i <= pos).ToList().ForEach(gs => { gs.Undone = false; });
            }
            else
            {
                var last = Snapshots.LastOrDefault(gs => !gs.Undone);
                if (last == null) return;
                var pos = Snapshots.IndexOf(last);
                pos += changeCount;
                if (pos < 0) pos = 0;
                var loaded = XamlReader.Load(XmlReader.Create(new StringReader(Snapshots[pos].Data))) as GObjectCollection;
                LoadGraph(loaded);
                Snapshots.Where((gs, i) => i > pos).ToList().ForEach(gs => { gs.Undone = true; });
            }
        }

        public void MoveToSnapshot(GraphSnapshot snapshot)
        {
            // TODO: since this snapshot method is inefficient, there is no need to optimize this process. just redo :)
            int pos = Snapshots.IndexOf(snapshot);
            if (pos < 0) return;
            var loaded = XamlReader.Load(XmlReader.Create(new StringReader(snapshot.Data))) as GObjectCollection;
            LoadGraph(loaded);
            Snapshots.Where((gs, i) => i > pos).ToList().ForEach(gs => { gs.Undone = true; });
            Snapshots.Where((gs, i) => i <= pos).ToList().ForEach(gs => { gs.Undone = false; });            
        }

        public void CreateSnapshot(string description)
        {
            var sb = new StringBuilder();
            XamlWriter.Save(this.Graph, XmlWriter.Create(sb));
            var last = Snapshots.LastOrDefault();
            while (last != null && last.Undone)
            {
                Snapshots.Remove(last);
                last = Snapshots.LastOrDefault();
            }
            Snapshots.Add(new GraphSnapshot(sb.ToString(), description ));
        }
    }
}
